#!/usr/bin/python2.5
#
# Copyright 2008 Google Inc. All Rights Reserved.

"""This application posts negative criteria at the mcc or account level."""

import copy
import getpass
import optparse
import sys

# Parameters can be hard-coded here.
dev_token = ''
app_token = ''
lib_home = '../thrid-party/google-api-adwords-python-lib'


def slurp(file_name):
  """Read file_name and return an array of lines of its contents."""
  if not file_name:
    return []
  return [x.strip() for x in file(file_name).readlines()]


def main(argv):

  parser = optparse.OptionParser(usage='usage: %prog [options]')
  parser.add_option('', '--user', dest='user',
                    help='Login for the MCC account.')
  parser.add_option('', '--password', dest='password',
                    help='Password for the MCC account.')
  parser.add_option('', '--account', dest='account',
                    help='Child account to operate on (defaults to user).')
  parser.add_option('', '--bad-urls', dest='urls_file', metavar='FILE',
                    help='List of websites to exclude.')
  parser.add_option('', '--bad-keywords', dest='keywords_file', metavar='FILE',
                    help='List of EXACT MATCH keywords to exclude.')
  parser.add_option('', '--commit',
                    action='store_true', dest='commit', default=False,
                    help='Write changes to AdWords. Cannot be undone.')
  parser.add_option('', '--clobber',
                    action='store_true', dest='clobber', default=False,
                    help='Overwrite existing criteria. DANGEROUS!')
  parser.add_option('', '--list-campaigns',
                    action='store_true', dest='list_campaigns', default=False,
                    help='List all affected campaigns.')
  parser.add_option('', '--list-criteria',
                    action='store_true', dest='info', default=False,
                    help='Show the current negative criteria.')
  parser.add_option('', '--not-accounts',
                    dest='excluded_accounts_file', metavar='FILE',
                    help='List of account IDs to exclude.')
  parser.add_option('', '--not-campaigns',
                    dest='excluded_campaigns_file', metavar='FILE',
                    help='List of campaign IDs to exclude.')
  parser.add_option('', '--dev-token', dest='dev_token',
                    help='AdWords API developer token.')
  parser.add_option('', '--app-token', dest='app_token',
                    help='AdWords API application token.')
  parser.add_option('', '--lib', dest='lib',
                    help='Location of google-api-adwords-python-lib.')
  options, args = parser.parse_args(argv)

  bad_urls_list = slurp(options.urls_file)
  bad_keywords_list = slurp(options.keywords_file)
  excluded_campaigns = slurp(options.excluded_campaigns_file)
  excluded_accounts = slurp(options.excluded_accounts_file)

  original_new_criteria = [{'criterionType': 'Website', 'url': x}
                           for x in bad_urls_list]
  original_new_criteria.extend(
      [{'criterionType': 'Keyword', 'type': 'Exact', 'text': x}
       for x in bad_keywords_list])

  if options.user:
    user = options.user
  else:
    user = getpass.getuser()

  if options.password:
    password = options.password
  else:
    password = getpass.getpass()

  if options.dev_token:
    dev_token = options.dev_token

  if options.app_token:
    app_token = options.app_token

  if options.lib:
    lib_home = options.lib

  if bad_urls_list:
    print 'New negative URLs:\n'
    for url in bad_urls_list:
      print ' ', url

  if bad_keywords_list:
    print 'New negative keywords:\n'
    for kw in bad_keywords_list:
      print ' ', kw

  if bad_urls_list or bad_keywords_list:
    print '\nwill be added as negative criteria for...\n'

  sys.path.append(lib_home)
  from aw_api.AccountService import AccountService
  from aw_api.Client import Client

  client = Client(headers={'email': user,
                           'password': password,
                           'useragent': 'Bulk Exclude Tool',
                           'developerToken': dev_token,
                           'applicationToken': app_token},
                  path=lib_home,
                  use_mcc=False)
  client.debug = False

  if options.account:
    startAccount = options.account
    client.SetClientEmail(startAccount)
  else:
    startAccount = user

  def processAccount(breadcrumb):

    info = client.GetAccountService().GetAccountInfo()
    customer_id = info[0]['customerId']

    if customer_id in excluded_accounts:
      return

    print 'Account %s (%s)' % (breadcrumb, customer_id)

    if options.list_campaigns or options.info or options.commit:
      campaigns = client.GetCampaignService().GetAllAdWordsCampaigns()
      for campaign in campaigns:
        new_criteria = copy.deepcopy(original_new_criteria)

        if (campaign['status'] != 'Deleted' and
            campaign['id'] not in excluded_campaigns):
          print '  Campaign %s: %r' % (campaign['id'], campaign['name'])
          criterion_service = client.GetCriterionService()

          if options.commit or options.info:
            criteria = criterion_service.GetCampaignNegativeCriteria(
                campaign['id'])

          if options.info:
            for criterion in criteria:
              prefix = '    Negative '
              if criterion['criterionType'] == 'Keyword':
                print prefix + 'keyword: %r' % criterion['text'].encode('utf-8')
              elif criterion['criterionType'] == 'Website':
                print prefix + 'URL: %r' % criterion['url']

          if options.commit:
            if not options.clobber:
              if len(criteria) != 1:
                old_criteria = [x._asdict() for x in criteria]
              else:
                old_criteria = criteria
              new_criteria.extend(old_criteria)
            criterion_service.SetCampaignNegativeCriteria(
                campaign['id'], new_criteria)

      print

    logins = client.GetAccountService().GetClientAccounts()
    for login in logins:
      client.SetClientEmail(login)
      processAccount(breadcrumb + ' > ' + login)

  processAccount(startAccount)

  if options.commit:
    print 'All campaigns updated successfully.'
  elif bad_urls_list:
    print 'To update these campaigns, run with the --commit flag.'

  print 'Used %s units for %s operations.' % (
      client.GetUnits(), client.GetOperations())


if __name__ == '__main__':
  main(sys.argv[1:])
